// gcc -o winm winm.c -lX11
// redhat: gcc -o winm winm.c -L/usr/X11R6/lib -lX11
// Xephyr :1 -ac / Xnest -ac :1
// export DISPLAY=:1 / xterm -display 1 &

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>

#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <locale.h>

#include <sys/wait.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#define VERSION "0.11"

// global variables definitions
static Display *dpy;
static Window rootWindow;
static int rWidth, rHeight;
static int screen1;

int (*xerrorxlib)(Display *, XErrorEvent *);

bool otherwm;
FILE *logfile;

// return the current time as a string with a predefined layout

void timetext(char *st)
{
    struct tm *ct;
    static time_t t;
    t = time(NULL);
    ct = localtime(&t);
    sprintf(st, "%02d%02d%02d %02d%02d%02d",
            ct->tm_year - 100, ct->tm_mon + 1, ct->tm_mday,
            ct->tm_hour, ct->tm_min, ct->tm_sec);
}

// logging function. a function that works like printf, but
// adds the source line from where it was called (supplied by the #define wmlog)
// number and outputs to both stderr and a file
#define wmlog(...) mylogger( __LINE__ , __VA_ARGS__ )

void mylogger(int line, char* fmt, ...)
{
    char text[510], textmore[550], thetime[100];

    va_list args;
    va_start(args, fmt);
    vsnprintf(text, 500, fmt, args);
    va_end(args);

    timetext(thetime);
    snprintf(textmore, 540, "[%s @ %d] [%s]\n", thetime, line, text);
    fprintf(logfile, "%s", textmore);
    fflush(logfile);
    fprintf(stderr, "%s", textmore);
    fflush(stderr);
}

// print some data about the supplied window

void printWinAttributes(Window w)
{
    XWindowAttributes rattrib;
    XGetWindowAttributes(dpy, w, &rattrib);
    wmlog("x:%d y:%d width:%d height:%d", rattrib.x, rattrib.y, rattrib.width, rattrib.height);
}

// print data about theWindow, and recursivly print data about all
// theWindow's children

void printWindowInfo(Window theWindow)
{
    Window rroot, rparent, *rchildren;
    XTextProperty wmname;
    int i;
    unsigned int rnum;

    XQueryTree(dpy, theWindow, &rroot, &rparent, &rchildren, &rnum);

    wmlog("This window is %X:", theWindow);
    printWinAttributes(theWindow);
    XGetWMName(dpy, theWindow, &wmname);

    wmlog("The WMNAME is %s", wmname.value);

    #ifdef MOREWININFO
    printWinAttributes(rroot);

    if (theWindow != rroot)
    {
        wmlog("rparent %X:", rparent);
        printWinAttributes(rparent);
    }
    #endif

    if (rnum > 0)
    {
        wmlog("This window has children, these are:");
        for (i = 0; i < rnum; i++)
        {
            wmlog("%X has child %X:", theWindow, rchildren[i]);
            printWindowInfo(rchildren[i]);
        }
    }
    XFree(rchildren);
}

// setup for winmalist

void start(void)
{
    rootWindow = DefaultRootWindow(dpy);
    rWidth = DisplayWidth(dpy, screen1);
    rHeight = DisplayHeight(dpy, screen1);
    wmlog("Screen... width:%d height:%d", rWidth, rHeight);
#define GRABKEY(XX) XGrabKey(dpy, XKeysymToKeycode(dpy,XK_##XX), ControlMask, rootWindow, True, GrabModeAsync, GrabModeAsync);
    GRABKEY(1);
    GRABKEY(2);
    GRABKEY(3);
    GRABKEY(4);
    GRABKEY(5);
    GRABKEY(6);

    GRABKEY(8);
    XSync(dpy, False);
}

// fork process and start a new xterm

void forkandxterm(void)
{
    switch (fork())
    {
        case 0:
            close(ConnectionNumber(dpy));
            execlp("xterm", "xterm", 0, NULL);
            exit(0);
            break;
        case -1 :
            wmlog("%s: couldn't fork");
            return;
            break;
        default:
            ;
    }
}

// gets a list of all open windows, and place and resize them next to eachother

void tilearrange(void)
{
    int i;
    unsigned int number;
    Window *list, rroot, rparent;
    XQueryTree(dpy, rootWindow, &rroot, &rparent, &list, &number);
    if (number > 0)
    {
        for (i = 0; i < number; i++)
        {
            XMoveResizeWindow(dpy,
                    list[i],
                    i * rWidth / number,
                    0,
                    rWidth / number,
                    rHeight);
        }
    }
    XFree(list);
}

// return the ID of the window that the mouse pointer hovers above (the active window)

int getfocuswindow(void)
{
    Window thewindow, root;
    int x1, x2, x3, x4;
    unsigned int x5;
    XQueryPointer(dpy, rootWindow, &root, &thewindow,
            &x1, &x2, &x3, &x4, &x5);
    return thewindow;
}

// destroys the active window

void destroywindow(void)
{
    int thewindow = getfocuswindow();
    if (thewindow != rootWindow)
    {
        XKillClient(dpy, thewindow);
        tilearrange();
    }
}

// maximizes the active window

void maximize(void)
{
    int thewindow = getfocuswindow();
    if (thewindow != rootWindow)
    {
        XRaiseWindow(dpy, thewindow);
        XMoveResizeWindow(dpy, thewindow, 0, 0, DisplayWidth(dpy, screen1), DisplayHeight(dpy, screen1));
    }
}

// rotate/circulate all open windows

void circulate(void)
{
    XCirculateSubwindowsUp(dpy, rootWindow);
    tilearrange();
}

// configure the window according to the event

void configurethewindow(XConfigureRequestEvent *ev)
{
    XWindowChanges chan;

    chan.x = ev->x;
    chan.y = ev->y;
    chan.width = ev->width;
    chan.height = ev->height;
    chan.border_width = ev->border_width;
    chan.sibling = ev->above;
    chan.stack_mode = ev->detail;

    XConfigureWindow(dpy, ev->window, ev->value_mask, &chan);
}

// main event loop of winmalist, listen to KeyPresses

void run(void)
{
#define IFKEY(XX) if(XLookupKeysym(&ev.xkey,0)==XK_##XX )
    XEvent ev;
    XSelectInput(dpy, rootWindow, KeyPressMask | SubstructureRedirectMask);

    while (true)
    {
        XNextEvent(dpy, &ev);
        switch (ev.type)
        {
            case KeyPress: // keycodes: include/X11/keysymdef.h

                IFKEY(1) forkandxterm();
                IFKEY(2) tilearrange();
                IFKEY(3) destroywindow();
                IFKEY(4) maximize();
                IFKEY(5) circulate();
                IFKEY(6) printWindowInfo(rootWindow);

                IFKEY(8) return;

                break;

            case KeyRelease:
                break; // do nothing on key release

            case MapRequest:
                wmlog("New window created");
                printWindowInfo(ev.xmaprequest.window);
                XMapWindow(dpy, ev.xmaprequest.window);
                break;

            case ConfigureRequest:
                configurethewindow(&(ev.xconfigurerequest));
                break;

            case CirculateRequest:
                wmlog("CirculateRequest is not implemented yet in this version");
                break;

            default:
                wmlog("Warning: got %d request that was not handled!", ev.type);
                ;
        }
    }
}

// callback when X signals error (check for another open wm)

int xerrorstart(Display *dpy, XErrorEvent *ee)
{
    otherwm = True;
    return -1;
}

// check if another wm is running

bool isanotherWM(void)
{
    otherwm = False;
    xerrorxlib = XSetErrorHandler(xerrorstart);
    XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); // reports x events
    XSync(dpy, False);
    return otherwm;
}

int main(int argc, char *argv[])
{
    if (argc == 2 && !strcmp("-V", argv[1]))
    {
        printf("winmalist version: %s\nState: beta\n", VERSION);
        return 0;
    }

    logfile = fopen("./winmalistlog.txt", "ab"); // append to previous file or create a new, open in binary mode
    if (logfile == NULL)
    {
        printf("could not open ./winmalistlog.txt for writing! exiting...\n");
        return -1;
    }

    dpy = XOpenDisplay(NULL); // connection to the X server, returns a struct

    if (dpy == NULL)
    {
        wmlog("Could not open display\nAnother window manager running?\n");
        return -1;
    }

    if (isanotherWM())
    {
        wmlog("another window manager is currently running\n"
                "Please stop it before continuing\n");
        return -1;
    }

    wmlog("DefaultDepth: %d", DefaultDepth(dpy, 0));
    wmlog("DefaultScreen: %d", DefaultScreen(dpy));

    start();
    run();
    XCloseDisplay(dpy);
    return 0;
}
